﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;
using System.Windows.Forms;
using System.ComponentModel;

namespace Easycmd
{
    // Structurs
    public struct ST_CopyFileDetails
    {

        String OriginalURI;
        String NewURI;

        // Constructor
        public ST_CopyFileDetails(String FromURI, String ToURI)
        {
            OriginalURI = FromURI;
            NewURI = ToURI;
        }

    }    //http://www.pinvoke.net/default.aspx/kernel32.CopyFileEx
    /// <summary>
    /// Copies a list of files or a directory tree to a destination
    /// 
    /// Support for GUI is implamented by the ICopyFilesDiag interface
    /// and passed to the class in the copy() method.
    /// </summary>
    public class CopyFilesCmd:CopyFileExWrapper{
        // Variables
        protected string srcDir = "";
        protected Int32 totalFiles = 0;
        protected Int32 totalFilesCopied = 0;
        protected String dstDir = "";
        protected List<String> files = new List<String>();
        private List<String> newFilenames = new List<String>();
        protected List<ST_CopyFileDetails> filesCopied = new List<ST_CopyFileDetails>();

        protected String currentFilename;
        protected Boolean cancel = false;
        protected IAsyncResult CopyResult;
        protected ICopyFilesDiag progressDlg;

        public delegate void DEL_cancelCopy();
        public delegate void DEL_copyComplete();
        public delegate void DEL_copyCanceled(List<ST_CopyFileDetails> filescopied);
        // Events
        protected delegate void CopyItemsDelegate();
        protected delegate void DEL_ShowDiag(ICopyFilesDiag diag);
        protected delegate void DEL_HideDiag(ICopyFilesDiag diag);
        protected delegate void DEL_CopyfilesCallback(IAsyncResult r);
        protected CopyItemsDelegate delCopy;
        

        public CopyFilesCmd(string srcDir, String dstDir)
        {            
            this.dstDir = dstDir;
            this.srcDir = srcDir;
        }
        protected  override void NotifyProgress(Int64 total, Int64 transferred, Int64 streamSize, Int64 StreamByteTrans, UInt32 dwStreamNumber, CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
        {
            //Check to see if there is a dialog window to use
            if (progressDlg != null)
            {
                //Are we going to send the update on the correct thread?
                if (progressDlg.SynchronizationObject != null && progressDlg.SynchronizationObject.InvokeRequired)
                {
                    progressDlg.SynchronizationObject.Invoke(new CopyProgressRoutine(CopyProgressHandler),
                                new Object[] { total, transferred, streamSize, StreamByteTrans, dwStreamNumber, reason, hSourceFile, hDestinationFile, lpData });
                }
                else
                {
                    progressDlg.update(totalFiles, totalFilesCopied, total, transferred, currentFilename);
                }

            }
        }
        protected void ShowDiag(ICopyFilesDiag diag)
        {
            //Check to see if there is a dialog window to use
            if (progressDlg != null)
            {
                //Are we going to send the update on the correct thread?
                if (progressDlg.SynchronizationObject != null && progressDlg.SynchronizationObject.InvokeRequired)
                {
                    progressDlg.SynchronizationObject.Invoke(new DEL_ShowDiag(ShowDiag),
                        new Object[] { diag });
                }
                else
                {
                    diag.Show();
                }
            }
        }
        protected void HideDiag(ICopyFilesDiag diag)
        {
            //Check to see if there is a dialog window to use
            if (progressDlg != null)
            {
                //Are we going to send the update on the correct thread?
                if (progressDlg.SynchronizationObject != null && progressDlg.SynchronizationObject.InvokeRequired)
                {
                    progressDlg.SynchronizationObject.Invoke(new DEL_HideDiag(HideDiag),
                        new Object[] { diag });
                }
                else
                {
                    diag.Hide();
                    cancel = false;
                }
            }
        }
        protected void CancelCopy()
        {
            cancel = true;
            OnCopyCanceled();
        }


        
        protected ConfirmResult result = ConfirmResult.Null;


        protected event DEL_copyComplete EV_copyComplete;
        protected event DEL_copyCanceled EV_copyCanceled;

        protected void OnCopyComplete()
        {
            if (EV_copyComplete != null)
            {
                EV_copyComplete();
            }
        }
        protected void OnCopyCanceled()
        {
            if (EV_copyCanceled != null)
            {
                EV_copyCanceled(filesCopied);
            }
        }




        // Async Callbacks
        protected void CopyItemCompleted(IAsyncResult r)
        {
            //Kill off the thread as its finished.
            delCopy.EndInvoke(CopyResult);
            HideDiag(progressDlg);
            OnCopyComplete();
        }




        public void CopyAsync(ISynchronizeInvoke syncObject)
        {
            progressDlg = new CopyProgressDlg();
            progressDlg.SynchronizationObject = syncObject;
            if (progressDlg != null && progressDlg.SynchronizationObject == null)
            {
                throw new Exception("Dialog window sent with no SynchronizationObject");
            }
            delCopy = new CopyItemsDelegate(CopyItems);
            CopyResult = delCopy.BeginInvoke(CopyItemCompleted, null);
        }
        public void AddFile(string p)
        {
            this.files.Add(Path.Combine(srcDir, p));
        }
        

        protected void CopyItems()
        {

            //Show the dialog box and hook into its cancel event if
            //a dialog box has been given
            try
            {
                if (progressDlg != null)
                {
                    progressDlg.EN_cancelCopy += CancelCopy;
                    ShowDiag(progressDlg);
                }
                totalFiles = FileUtils.GetTotalFiles(files, srcDir);
                //Loop each file and copy it.
                foreach (String filename in files.ToArray())
                {
                    if (cancel)
                    {
                        break;
                    }
                    if (File.Exists(filename))
                    {
                        CopyFileToDir(filename, dstDir);

                    }
                    else if (Directory.Exists(filename))
                    {
                        CopyDir(filename);
                    }
                }
            }catch(Exception Exception ){
                MessageBox.Show(Exception.Message);
            }
        }
        protected void CopyFileToDir(String filename, string dstPath)
        {
            String dstFilepath = System.IO.Path.Combine(dstPath, new FileInfo(filename).Name);
            CopyFileToFile(filename, dstFilepath);
        }

        private void CopyFileToFile(String filename, String dstFileName)
        {
            FileUtils.EnsureDirExistsFor(dstFileName);
            currentFilename = filename;
            unsafe
            {
                fixed (Boolean* cancelp = &cancel)
                {
                    bool overwrite = ConfirmOverwrite(dstFileName);
                    if (!CopyFileEx(filename, dstFileName, new CopyProgressRoutine(this.CopyProgressHandler), IntPtr.Zero, cancelp, overwrite ? 0 : CopyFileFlags.COPY_FILE_FAIL_IF_EXISTS))
                    {
                        //MessageBox.Show(new Win32Exception(Marshal.GetLastWin32Error()).Message);
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    }
                }
            }
            filesCopied.Add(new ST_CopyFileDetails(filename, dstFileName));
            totalFilesCopied += 1;
        }

        protected  void CopyDir(string dirName)
        {
            DirectoryInfo dir = new DirectoryInfo(dirName);
            foreach (FileInfo fileInfo in dir.GetFiles())
            {
                string newDir = GetDir(fileInfo.FullName).Replace(srcDir, dstDir);
                CopyFileToDir(fileInfo.FullName, newDir);
            }
            foreach (DirectoryInfo dirInfo in dir.GetDirectories())
            {
                CopyDir(dirInfo.FullName);
            }
        }
        protected static String GetDir(String fullFileName)
        {
            String tempDirPath = "";
            String[] filepath;
            //Save the new DIR path and check the DIR exsits,
            //if it does not then create it so the files can copy
            filepath = fullFileName.Split('\\');
            for (int i = 0; i < filepath.Length - 1; i++)
            {
                tempDirPath += filepath[i] + "\\";
            }
            return tempDirPath;
        }
        

        protected bool ConfirmOverwrite(String dstFilepath)
        {
            bool overwrite = false;
            string info = string.Format("file {0} \nwill be overwrite by \n{1},\n\ncontinue?", dstFilepath, currentFilename);
            string caption = "confirm overwrite";
            if (File.Exists(dstFilepath))
            {
                if (result == ConfirmResult.YesToAll)
                {
                    overwrite = true;
                }
                else if (result == ConfirmResult.NoToAll)
                {
                    overwrite = true;
                }
                else
                {
                    result = FileOverwriteConfirmDlg.Show(info, caption);
                    overwrite = result == ConfirmResult.YesToAll || result == ConfirmResult.Yes;
                }
            }
            return overwrite;
        }
        //protected delegate CopyProgressResult DEL_CopyProgressHandler(Int64 total, Int64 transferred, Int64 streamSize, Int64 StreamByteTrans, UInt32 dwStreamNumber, CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData);



        public void DuplicateAsync(ISynchronizeInvoke syncObject)
        {
            progressDlg = new CopyProgressDlg();
            progressDlg.SynchronizationObject = syncObject;
            if (progressDlg != null && progressDlg.SynchronizationObject == null)
            {
                throw new Exception("Dialog window sent with no SynchronizationObject");
            }
            delCopy = new CopyItemsDelegate(DuplicateItems);
            CopyResult = delCopy.BeginInvoke(CopyItemCompleted, null);
        }
        protected void DuplicateDir(string dirName,string toDir)
        {
            DirectoryInfo dir = new DirectoryInfo(dirName);
            foreach (FileInfo fileInfo in dir.GetFiles())
            {
                //string newDir = GetDir(fileInfo.FullName).Replace(srcDir, dstDir);
                CopyFileToDir(fileInfo.FullName, toDir);
            }
            foreach (DirectoryInfo dirInfo in dir.GetDirectories())
            {
                DuplicateDir(dirInfo.FullName,Path.Combine(toDir,dirInfo.Name));
            }
        }
        protected void DuplicateItems()
        {

            //Show the dialog box and hook into its cancel event if
            //a dialog box has been given
            try
            {
                if (progressDlg != null)
                {
                    progressDlg.EN_cancelCopy += CancelCopy;
                    ShowDiag(progressDlg);
                }
                totalFiles = FileUtils.GetTotalFiles(files, srcDir);
                //Loop each file and copy it.
                foreach (String filename in files.ToArray())
                {
                    if (cancel)
                    {
                        break;
                    }
                    if (File.Exists(filename))
                    {
                        CopyFileToFile(filename, FileUtils.GetNumberPostFixFileName(filename));

                    }
                    else if (Directory.Exists(filename))
                    {
                        DuplicateDir(filename, FileUtils.GetNumberPostFixFileName(filename));
                    }
                }
            }
            catch (Exception Exception)
            {
                MessageBox.Show(Exception.Message);
            }
        }

      
    }
    public abstract class CopyFileExWrapper
    {

        
        // Delegates
        protected delegate CopyProgressResult CopyProgressRoutine(Int64 TotalFileSize, Int64 TotalBytesTransferred, Int64 StreamSize, Int64 StreamBytesTransferred, UInt32 dwStreamNumber, CopyProgressCallbackReason dwCallbackReason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData);
        
        
    

        // Enums
        // These Enums are used for the windows CopyFileEx function
        [Flags]
        protected enum CopyFileFlags : uint
        {
            COPY_FILE_FAIL_IF_EXISTS = 0x00000001,
            COPY_FILE_RESTARTABLE = 0x00000002,
            COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004,
            COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008
        }
        protected enum CopyProgressResult : uint
        {
            PROGRESS_CONTINUE = 0,
            PROGRESS_CANCEL = 1,
            PROGRESS_STOP = 2,
            PROGRESS_QUIET = 3
        }
        protected enum CopyProgressCallbackReason : uint
        {
            CALLBACK_CHUNK_FINISHED = 0x00000000,
            CALLBACK_STREAM_SWITCH = 0x00000001
        }

        
        

        // Kernal32 Calls
        // Unsafe is need to show that we are using 
        // pointers which are classed as "unsafe" in .net
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        [return: MarshalAs(UnmanagedType.Bool)]
        protected static extern unsafe bool CopyFileEx(string lpExistingFileName, string lpNewFileName, CopyProgressRoutine lpProgressRoutine, IntPtr lpData, Boolean* pbCancel, CopyFileFlags dwCopyFlags);
        protected CopyProgressResult CopyProgressHandler(Int64 total, Int64 transferred, Int64 streamSize, Int64 StreamByteTrans, UInt32 dwStreamNumber, CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
        {
            NotifyProgress(total, transferred, streamSize, StreamByteTrans, dwStreamNumber, reason, hSourceFile, hDestinationFile, lpData);
            return CopyProgressResult.PROGRESS_CONTINUE;
        }
        protected abstract void NotifyProgress(Int64 total, Int64 transferred, Int64 streamSize, Int64 StreamByteTrans, UInt32 dwStreamNumber, CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData);
        
    }

    //The interface for the Dialog the CopyFiles class uses.
    public interface ICopyFilesDiag
    {
        //needed to sync the CopyClass update events with the dialog thread
        System.ComponentModel.ISynchronizeInvoke SynchronizationObject { get; set; }

        //This event should fire when you want to cancel the copy
        event CopyFilesCmd.DEL_cancelCopy EN_cancelCopy;

        //This is how the CopyClass will send your dialog information about
        //the transfer
        void update(Int32 totalFiles, Int32 copiedFiles, Int64 totalBytes, Int64 copiedBytes, String currentFilename);
        void Show();
        void Hide();
    }
}
